
<!--     //正方体
    createRect() { this.svg.append('rect').attr('height', 100).attr('width', 100).attr('fill', '#000') },
    //circle
    createRect() { this.svg.append('circle').attr('cx', 100).attr('cy', 100).attr('r', 100).attr('fill', '#000') },
    //椭圆
    createEllipse() { this.svg.append('ellipse').attr('cx', 100).attr('cy', 100).attr('rx', 100).attr('ry', 50).attr('fill', '#000') },
    //直线
    createLine() { this.svg.append('line').attr('x1', 100).attr('y1', 100).attr('x2', 200).attr('y2', 200).attr('stroke', '#000') },
    //path
    createPath() { this.svg.append('path').attr('d', 'M 100,100 200,200').attr('stroke', '#000').attr('marker-start', 'url(#posMarker)').attr('marker-end', 'url(#posActMarker)') },
 -->
 <template>
  <div>
    <div class="svg_wrap"></div>
    <div class="control_wrap">
      <button @click="restartForce">重新启动仿真:restart()</button>
      <button @click="stopForce">重新启动仿真:stop()</button>
    </div>
    <div class="level-1">
      <div class="level-2">
        <div class="level-3"></div>
      </div>
    </div>
    <div class="level-2">
      <div class="level-3"></div>
      <div class="level-3"></div>
    </div>
    <div class="level-1">
      <div class="level-2">
        <div class="level-3"></div>
        <div class="level-3"></div>
        <div class="level-3"></div>
      </div>
    </div>
    <div class="test_svg">
      <svg>
        <line x1="0" x2="100" y1="0" y2="100" style="stroke-width: 4;stroke: #000"></line>
      </svg>
    </div>
  </div>
</template>
<script>
import * as d3 from 'd3'
// import install from '@/plugins/d3-context-menu'
// install(d3) // 为d3注册右键菜单插件
export default {
  data() {
    return {
      nodes: [
        { "id": 1, "name": "一", relation: '111111111111111', level: 1, children: [{ id: 11, pid: 1, name: '1-1', relation: '1', level: 2, children: [{ id: 99, pid: 11, name: '1-2', relation: '2', level: 3, }] }, { id: 12, pid: 1, name: '1-3', relation: '1', level: 2, }, { id: 13, pid: 1, name: '1-4', relation: '1', level: 2, }] },
        { "id": 2, "name": "二", relation: '2', children: [{ id: 22, pid: 2, name: '2-2', relation: '3' }, { id: 23, pid: 2, name: '2-2', relation: '3' }] },
        // { "id": 3, "name": "\u5468\u83b9", children: [{ id: 33, pid: 3, name: '3-3',relation: '其他' }, { id: 32, pid: 3, name: '3-2',relation: '其他' }, { id: 33, name: '3-3',relation: '其他' }] },
      ],
      forceSimulation: null,//力-实例
      svg: null,//元素
      line: null,//线条
      node: null,//节点
      lineText: null,//线条文字
      colors: ['#55cccc', '#aaaaff', '#4e88af', '#ca635f', '#FFC0CB', '#BA55D3', '#1E90FF', '#7FFFD4', '#FFFF00'],
      links: [],

      isclickNode: false,
    }
  },
  methods: {
    init() {
      this.initData()
      this.initRender()//创建画布
      this.addMarkers()//创建数据
      this.d3SelectData()
      this.initForceSimulation()//初始化力学模拟
    },
    //初始化数据
    initData() {
      let nodes = JSON.parse(JSON.stringify(this.nodes))
      this.nodes = this.formateData(nodes)
      //// console.log(this.nodes, 'this.formateData(nodes)')
      console.log(this.nodes, '')
      for (let i = 0; i < this.nodes.length - 1; i++) {
        for (let j = i + 1; j < this.nodes.length; j++) {
          if (this.nodes[i].id == this.nodes[j].pid) {
            console.log(this.nodes[j].relation, 'this.nodes[j].relation')
            this.links.push({ source: this.nodes[i].id, target: this.nodes[j].id, relation: this.nodes[j].relation })
          } else if (this.nodes[i].pid == this.nodes[j].id) {
            console.log(this.nodes[j], this.nodes[j].relation, 'this.nodes[j].relation')
            this.links.push({ source: this.nodes[j].id, target: this.nodes[i].id, relation: this.nodes[j].relation })
          }
        }
      }
    },
    //格式化数据
    formateData(nodes) {
      let arr = []
      nodes.forEach((item, index) => {
        if (item.children && item.children.length) { arr.push(...this.formateData(item.children)) }
        arr.push({ ...item, id: item.id, name: item.name, pid: item.pid, relation: item.relation })
      })
      return arr
    },
    //初始化渲染
    initRender() {
      this.svg = d3.select('.svg_wrap').append('svg').attr('width', '100%').attr('height', '800px')
        // 给画布绑定zoom事件（缩放、平移）
        .call(d3.zoom().on('zoom', event => {
          var scale = event.transform.k, translate = [event.transform.x, event.transform.y]
          // this.svg.attr('transform', 'translate(' + translate[0] + ', ' + translate[1] + ') scale(' + scale + ')');
          this.svg.attr("transform", event.transform)
        })).append('g').attr('width', '100%').attr('height', '100%')
    },





    //d3 select data
    d3SelectData() {

      //绘制边上的文字
      // this.lineText = this.svg.append('g').attr("class", "lineText_wrap").selectAll('text').data(this.links).enter().append('text').text(function (d, i) { return d.relation; });
      this.lineWrap = this.svg.selectAll('g.lineText_wrap').data(this.links).enter().append('g').attr("class", "lineText_wrap")
      //连线
      this.line = this.lineWrap.append('g').attr('class', 'line_wrap').append('line').attr('stroke', '#000').attr('stroke-width', 2).attr('opacity', 1).attr("marker-end", "url(#posMarker)")
      //连线-矩形
      this.lineRect = this.lineWrap.append('g').attr('class', 'line_rect').append('rect').attr('width', function (d, i, g) {
        // lineWrap.each((a,b,c) => {})
        console.log(d3.select(this).parent, 'd3.select(this)')
      }).attr('height', 20).attr('x', -25).attr('y', -10).attr('fill', '#fff').attr('stroke', '#000')
      //连线-内容
      this.lineText = this.lineWrap.append('g').attr('class', 'line_text').append('text').attr('text-anchor', 'middle').text(function (d, i) { return d.relation; });

      //节点
      this.node = this.svg.append('g').attr('class', 'node_wrap').selectAll('circle').data(this.nodes).enter().append('circle')
        .attr('r', d => { let size = 30, level = d.level && parseInt(d.level) || 1; return size - ((level - 1) * 3) })
        .attr("fill", d => { return this.colors[d.level ? d.level : this.colors.length - 1] })
        .attr("stroke", "none")
        .attr("name", d => d.name)
        .attr("id", d => d.id)
        .style("cursor", "pointer")
        .on("click", this.nodeClick)//点击
        .on('mouseover', this.mouseoverNode)
        .on('mouseleave', this.mouseleaveNode)
        .call(this.drag())//拖拽
      console.log(this.svg.node().parentElement.clientWidth / 2, this.svg.node().parentElement.clientHeight / 2, 'svg.node().parentElement')
    },

    //力学模拟
    initForceSimulation() {
      // 定义碰撞检测模型
      this.forceCollide = d3.forceCollide()
        .radius(d => { return 46 })//半径访问器-将节点视为具有一定 radius 的圆
        .strength(0.5)//碰撞力的强度
        .iterations(0.15) //迭代次数


      //力
      this.forceSimulation = d3.forceSimulation(this.nodes)//创建 力模拟实力
        //为力模型新加某种力
        .force("link", d3.forceLink(this.links).id(d => d.id).distance(200))//弹簧力-弹簧模型,力的强度与被链接两个节点的距离成比例
        .force("charge", d3.forceManyBody().strength(-300))//电荷力
        .force("center", d3.forceCenter(this.svg.node().parentElement.clientWidth / 2, this.svg.node().parentElement.clientHeight / 2))//向心力-用指定的x坐标和y坐标创建一个居中力
        .force("collision", this.forceCollide)//碰撞检测
        // .force("positioning", d3.forceRadial(400, this.svg.node().parentElement.clientWidth / 2, this.svg.node().parentElement.clientHeight / 2))//碰撞检测
        .on("tick", this.ticked)//监听力学
    },

    getTransform(source, target, _dis) {
      var r;
      if (target.x > source.x) {
        if (target.y > source.y) {
          r = Math.asin((target.y - source.y) / _dis)
        } else {
          r = Math.asin((source.y - target.y) / _dis);
          r = -r;
        }
      } else {
        if (target.y > source.y) {
          r = Math.asin((target.y - source.y) / _dis);
          r = Math.PI - r;
        } else {
          r = Math.asin((source.y - target.y) / _dis);
          r -= Math.PI;
        }
      }
      r = r * (180 / Math.PI);
      return "translate(" + source.x + "," + source.y + ")rotate(" + r + ")";
    },
    // 求两点间的距离
    getDis(s, t) { return Math.sqrt((s.x - t.x) * (s.x - t.x) + (s.y - t.y) * (s.y - t.y)) },
    // ticked()函数确定link线的起始点x、y坐标 node确定中心点 文本通过translate平移变化
    ticked() {
      // console.log(this.links)

      //lineWrap
      this.lineWrap.attr("transform", d => {
        return this.getTransform(d.source, d.target, this.getDis(d.source, d.target))
      });
      this.line.attr("x1", (d, i , el) => {
        console.log(d, i , el,'el-------')
      }).attr("y1", 0).attr("x2", d => this.getDis(d.source, d.target) - 30 - 10).attr("y2", d => { console.log(d); return 0 })
      this.lineRect.attr("x", d => this.getDis(d.source, d.target) / 2 - 25)
      this.lineText.attr("x", d => {
        return this.getDis(d.source, d.target) / 2
      }).attr("y", 5).attr("transform", (d) => {
        // 7.3.3 更新文本反正
        if (d.target.x < d.source.x) {
          var x = this.getDis(d.source, d.target) / 2;
          return 'rotate(180 ' + x + ' ' + 0 + ')';
        } else {
          return 'rotate(0)';
        }
      });





      this.node.attr("cx", d => d.x).attr("cy", d => d.y)







      return;
      this.line.attr("x1", d => d.source.x).attr("y1", d => d.source.y).attr("x2", d => d.target.x).attr("y2", d => d.target.y)
      this.node.attr("cx", d => d.x).attr("cy", d => d.y)
      // this.lineRect.attr("x", function (d) { return (d.source.x + d.target.x) / 2 - 25 }).attr("y", function (d) { return (d.source.y + d.target.y) / 2 - 12 }).attr('transform', d => {

      // })
      this.lineText
        .attr('transform', d => {
          let x = (d.source.x + d.target.x) / 2 //定位
          let y = (d.source.y + d.target.y) / 2 //定位
          // tanA = a / b
          // A = arctan(tanA)
          let tanA = Math.abs(d.source.y - d.target.y) / Math.abs(d.source.x - d.target.x)
          let angle = Math.atan(tanA) / Math.PI * 180
          // let angle = Math.atan2(1,1)/Math.PI*180
          // console.log(angle)
          // 第一、二象限额外处理
          if (d.source.x > d.target.x) {//第二、三象限
            // 第二象限
            if (d.source.y <= d.target.y) {
              angle = -angle
            }
            // else {  // 第三象限
            //   angle = angle
            // }
          } else if (d.source.y > d.target.y) {//三四象限
            // 第一象限
            angle = -angle
          }
          console.log(x, y, angle, '-----ddd')
          return 'translate(' + x + ',' + y + ')' + 'rotate(' + angle + ')'
        })
    },
    //点击事件
    nodeClick(event, data) {
      this.isclickNode = true
      this.mouseoverNode(event, data)
    },
    // 绘制关系箭头
    addMarkers() {
      // 定义箭头的标识
      var defs = this.svg.append("defs")
      // const posMarker = defs.append("marker").attr("id", "posMarker").attr("orient", "auto").attr("stroke-width", 2).attr("markerUnits", "strokeWidth").attr("markerUnits", "userSpaceOnUse").attr("viewBox", "0 -5 10 10").attr("refX", 30).attr("refY", 0).attr("markerWidth", 12).attr("markerHeight", 12).append("path").attr("d", "M 0 -5 L 10 0 L 0 5").attr('fill', '#000').attr("stroke-opacity", 0.6);
      // const posActMarker = defs.append("marker").attr("id", "posActMarker").attr("orient", "auto").attr("stroke-width", 2).attr("markerUnits", "strokeWidth").attr("markerUnits", "userSpaceOnUse").attr("viewBox", "0 -5 10 10").attr("refX", 0).attr("refY", 0).attr("markerWidth", 12).attr("markerHeight", 12).append("path").attr("d", "M 0 -5 L 10 0 L 0 5").attr('fill', '#1E90FF').attr("stroke-opacity", 0.6);
      const posMarker = defs.append("marker").attr("id", "posMarker").attr("orient", "auto").attr("stroke-width", 2).attr("markerUnits", "strokeWidth").attr("markerUnits", "userSpaceOnUse").attr("viewBox", "0 -5 10 10").attr("markerWidth", 12).attr("markerHeight", 12).append("path").attr("d", "M 0 -5 L 10 0 L 0 5").attr('fill', '#000').attr("stroke-opacity", 0.6);
      const posActMarker = defs.append("marker").attr("id", "posActMarker").attr("orient", "auto").attr("stroke-width", 2).attr("markerUnits", "strokeWidth").attr("markerUnits", "userSpaceOnUse").attr("viewBox", "0 -5 10 10").attr("markerWidth", 12).attr("markerHeight", 12).append("path").attr("d", "M 0 -5 L 10 0 L 0 5").attr('fill', '#1E90FF').attr("stroke-opacity", 0.6);
    },
    //node 拖拽事件
    drag() {
      let _this = this
      function dragsubject(event) {
        // console.log(event, 'dragsubject')
        return _this.forceSimulation.find(event.x, event.y);
      }

      function dragstarted(event) {
        // console.log(event, 'dragstarted')
        if (!event.active) _this.forceSimulation.alphaTarget(0.3).restart();
        event.subject.fx = event.subject.x;
        event.subject.fy = event.subject.y;
      }

      function dragged(event) {
        // console.log(event, 'dragged')
        event.subject.fx = event.x;
        event.subject.fy = event.y;
      }

      function dragended(event) {
        // console.log(event, 'dragended')
        if (!event.active) _this.forceSimulation.alphaTarget(0);
        // 注释以下代码，使拖动结束后固定节点
        event.subject.fx = null;//如果想要某个节点解除固定，则将 node.fx 和 node.fy 设置为 null 或者删除这两个属性。
        event.subject.fy = null;//如果想要某个节点解除固定，则将 node.fx 和 node.fy 设置为 null 或者删除这两个属性。
      }

      return d3.drag()
        .subject(dragsubject)
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended)
    },
    //鼠标移入node节点上
    mouseoverNode(event, data) {
      //节点
      console.log(this.svg.select('.node_wrap').selectAll('circle'), '11')
      this.svg.select('.node_wrap').selectAll('circle').attr('class', (d) => {
        console.log(data.id, d.id, d.pid, d, data.id == d.id, 'this.nodes')
        if (data.id == d.id || data.id == d.pid || data.pid == d.id) {//本身、父级、子级
          return 'actived'
        } else { return this.isclickNode ? 'disactived' : '' }
      })

      //line
      this.line.attr('class', (d) => {
        if (d['source'].id == data.id || d['target'].id == data.id) {
          return 'actived'
        } else { return this.isclickNode ? 'disactived' : '' }
      })
      //lineText
      this.lineText.attr('class', (d) => {
        if (d['source'].id == data.id || d['target'].id == data.id) {//本身、父级、子级
          return 'actived'
        } else { return this.isclickNode ? 'disactived' : '' }
      })
    },
    //鼠标离开节点
    mouseleaveNode(event, data) {
      // 移除所有样式
      this.svg.select('.node_wrap').selectAll('circle').attr('class', '')
      this.svg.select(".lineText_wrap").selectAll('text').attr('class', '')
      this.svg.select('.line_wrap').selectAll('line').attr('class', '').attr('marker-end', 'url(#posMarker)')
      // this.svg.select(".linkTexts").selectAll('text').attr('class', '')
    },
    //控制器
    restartForce() { console.log('this.forceSimulation.restart()'); this.forceSimulation.restart() },//重新启动仿真
    stopForce() { console.log('this.forceSimulation.stop()'); this.forceSimulation.stop() },//暂停仿真
  },

  mounted() {
    let selection = d3.selectAll('.level-1').selectAll('.level-2').attr('class')
    // this.init()
  }
}
</script> 

<style lang="scss" >
// node节点样式
.svg_wrap {
  background: #eee;
}

.node_wrap circle {
  border: 10px #ff6a6a solid;

  //激活
  &.actived {
    stroke: #ffc0cb;
    stroke-width: 14px;
    stroke-opacity: 0.15 + 0.3;
    fill: red;
    border: 10px #ff6a6a solid;
  }

  //失效
  &.disactived {
    stroke: #ffc0cb;
    stroke-width: 14px;
    stroke-opacity: 0.15 + 0.3;
    background: red;
    border: 10px #ff6a6a solid;
  }
}

//线条
.line_wrap {
  //线条
  text {
    &.actived {
      stroke: #ffc0cb;
      stroke-width: 14px;
      stroke-opacity: 0.15 + 0.3;
      background: red;
      border: 10px #ff6a6a solid;
    }
  }
}

//线条文字
.lineText_wrap {
  text {
    &.actived {
    }
  }
}

.node_wrap circle.disactived,
.lineText_wrap text.disactived,
.line_wrap line.disactived {
  opacity: 0.1;
}

.control_wrap {
  position: fixed;
  right: 0;
  top: 0;
  background: #eee;
  z-index: 999;
  padding: 20px;
}

/* //节点样式 */
</style>
